{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "uuid": "22974da6-54ec-4cfe-a278-819b0aa3a7ed"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>sent1</th>\n",
       "      <th>sent2</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>怎么更改花呗手机号码</td>\n",
       "      <td>我的花呗是以前的手机号码，怎么更改成现在的支付宝的号码手机号</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>也开不了花呗，就这样了？完事了</td>\n",
       "      <td>真的嘛？就是花呗付款</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>花呗冻结以后还能开通吗</td>\n",
       "      <td>我的条件可以开通花呗借款吗</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>如何得知关闭借呗</td>\n",
       "      <td>想永久关闭借呗</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>花呗扫码付钱</td>\n",
       "      <td>二维码扫描可以用花呗吗</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>花呗逾期后不能分期吗</td>\n",
       "      <td>我这个 逾期后还完了 最低还款 后 能分期吗</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>花呗分期清空</td>\n",
       "      <td>花呗分期查询</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>借呗逾期短信通知</td>\n",
       "      <td>如何购买花呗短信通知</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>借呗即将到期要还的账单还能分期吗</td>\n",
       "      <td>借呗要分期还，是吗</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>花呗为什么不能支付手机交易</td>\n",
       "      <td>花呗透支了为什么不可以继续用了</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "               sent1                           sent2  label\n",
       "1         怎么更改花呗手机号码  我的花呗是以前的手机号码，怎么更改成现在的支付宝的号码手机号      1\n",
       "2    也开不了花呗，就这样了？完事了                      真的嘛？就是花呗付款      0\n",
       "3        花呗冻结以后还能开通吗                   我的条件可以开通花呗借款吗      0\n",
       "4           如何得知关闭借呗                         想永久关闭借呗      0\n",
       "5             花呗扫码付钱                     二维码扫描可以用花呗吗      0\n",
       "6         花呗逾期后不能分期吗          我这个 逾期后还完了 最低还款 后 能分期吗      0\n",
       "7             花呗分期清空                          花呗分期查询      0\n",
       "8           借呗逾期短信通知                      如何购买花呗短信通知      0\n",
       "9   借呗即将到期要还的账单还能分期吗                       借呗要分期还，是吗      0\n",
       "10     花呗为什么不能支付手机交易                 花呗透支了为什么不可以继续用了      0"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "data_df = pd.read_csv(\"atec_nlp_sim_train_all.csv\", sep=\"\\t\", header=None, \n",
    "                      encoding=\"utf-8-sig\", names=[\"sent1\", \"sent2\", \"label\"])\n",
    "data_df.head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "uuid": "8e96c543-d70f-4beb-a65c-5e147a7979dc"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Loading model from cache /tmp/jieba.cache\n",
      "Loading model cost 1.179 seconds.\n",
      "Prefix dict has been built successfully.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['怎么', '更改', '花呗', '手机号码']\n"
     ]
    }
   ],
   "source": [
    "import jieba\n",
    "\n",
    "# 添加自定义词典\n",
    "jieba.load_userdict(\"atec_dict.txt\")\n",
    "# 分词测试\n",
    "seg_words = jieba.lcut(\"怎么更改花呗手机号码\")\n",
    "print(seg_words)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "uuid": "28ec6812-e07f-4b9a-b461-e7e5897efe50"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>sent1</th>\n",
       "      <th>sent2</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[怎么, 更改, 花呗, 手机号码]</td>\n",
       "      <td>[我, 的, 花呗, 是, 以前, 的, 手机号码, ，, 怎么, 更, 改成, 现在, 的...</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[也, 开, 不了, 花呗, ，, 就, 这样, 了, ？, 完事, 了]</td>\n",
       "      <td>[真的, 嘛, ？, 就是, 花呗, 付款]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>[花呗, 冻结, 以后, 还, 能, 开通, 吗]</td>\n",
       "      <td>[我, 的, 条件, 可以, 开通, 花呗, 借款, 吗]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>[如何, 得知, 关闭, 借呗]</td>\n",
       "      <td>[想, 永久, 关闭, 借呗]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>[花呗, 扫码, 付钱]</td>\n",
       "      <td>[二维码, 扫描, 可以, 用, 花呗, 吗]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>[花呗, 逾期, 后, 不能, 分期, 吗]</td>\n",
       "      <td>[我, 这个,  , 逾期, 后, 还, 完, 了,  , 最低, 还款,  , 后,  ,...</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>[花呗, 分期, 清空]</td>\n",
       "      <td>[花呗, 分期, 查询]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>[借呗, 逾期, 短信, 通知]</td>\n",
       "      <td>[如何, 购买, 花呗, 短信, 通知]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>[借呗, 即将, 到期, 要, 还, 的, 账单, 还, 能, 分期, 吗]</td>\n",
       "      <td>[借呗, 要, 分期, 还, ，, 是, 吗]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>[花呗, 为什么, 不能, 支付, 手机, 交易]</td>\n",
       "      <td>[花呗, 透支, 了, 为什么, 不, 可以, 继续, 用, 了]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                     sent1  \\\n",
       "1                       [怎么, 更改, 花呗, 手机号码]   \n",
       "2    [也, 开, 不了, 花呗, ，, 就, 这样, 了, ？, 完事, 了]   \n",
       "3                [花呗, 冻结, 以后, 还, 能, 开通, 吗]   \n",
       "4                         [如何, 得知, 关闭, 借呗]   \n",
       "5                             [花呗, 扫码, 付钱]   \n",
       "6                   [花呗, 逾期, 后, 不能, 分期, 吗]   \n",
       "7                             [花呗, 分期, 清空]   \n",
       "8                         [借呗, 逾期, 短信, 通知]   \n",
       "9   [借呗, 即将, 到期, 要, 还, 的, 账单, 还, 能, 分期, 吗]   \n",
       "10               [花呗, 为什么, 不能, 支付, 手机, 交易]   \n",
       "\n",
       "                                                sent2  label  \n",
       "1   [我, 的, 花呗, 是, 以前, 的, 手机号码, ，, 怎么, 更, 改成, 现在, 的...      1  \n",
       "2                              [真的, 嘛, ？, 就是, 花呗, 付款]      0  \n",
       "3                       [我, 的, 条件, 可以, 开通, 花呗, 借款, 吗]      0  \n",
       "4                                     [想, 永久, 关闭, 借呗]      0  \n",
       "5                             [二维码, 扫描, 可以, 用, 花呗, 吗]      0  \n",
       "6   [我, 这个,  , 逾期, 后, 还, 完, 了,  , 最低, 还款,  , 后,  ,...      0  \n",
       "7                                        [花呗, 分期, 查询]      0  \n",
       "8                                [如何, 购买, 花呗, 短信, 通知]      0  \n",
       "9                             [借呗, 要, 分期, 还, ，, 是, 吗]      0  \n",
       "10                  [花呗, 透支, 了, 为什么, 不, 可以, 继续, 用, 了]      0  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_df[\"sent1\"] = data_df[\"sent1\"].apply(lambda x: jieba.lcut(x.strip(\"\\r\\t \").replace(\"***\",\"*\")))\n",
    "data_df[\"sent2\"] = data_df[\"sent2\"].apply(lambda x: jieba.lcut(x.strip(\"\\r\\t \").replace(\"***\",\"*\")))\n",
    "data_df.head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "uuid": "4dccae3f-490a-4aa6-bf02-f7f088eaa4f5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('花呗', 141731), ('我', 61743), ('借呗', 61340), ('的', 60189), ('了', 47468), ('，', 46908), ('吗', 42196), ('还', 35076), ('怎么', 33715), ('还款', 29525)]\n"
     ]
    }
   ],
   "source": [
    "from collections import Counter\n",
    "\n",
    "c = Counter()\n",
    "sent_data = data_df[\"sent1\"].values + data_df[\"sent2\"].values\n",
    "for d in sent_data:\n",
    "    c.update(d)\n",
    "word_counts = sorted(dict(c).items(), key=lambda x: x[1], reverse=True)\n",
    "\n",
    "print(word_counts[:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "uuid": "1254dd09-dcdb-42e4-8852-e90f0c65e66c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "vocab size:  13262\n",
      "[('<PAD>', 0), ('<UNK>', 1), ('花呗', 2), ('我', 3), ('借呗', 4)]\n",
      "[(0, '<PAD>'), (1, '<UNK>'), (2, '花呗'), (3, '我'), (4, '借呗')]\n"
     ]
    }
   ],
   "source": [
    "vocab_words = [\"<PAD>\", \"<UNK>\"]\n",
    "for w, c in word_counts:\n",
    "    vocab_words.append(w)\n",
    "\n",
    "vocab2id = {w: i for i, w in enumerate(vocab_words)}\n",
    "id2vocab = {i: w for i, w in enumerate(vocab_words)}\n",
    "\n",
    "print(\"vocab size: \", len(vocab2id))\n",
    "print(list(vocab2id.items())[:5])\n",
    "print(list(id2vocab.items())[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "uuid": "7ffb7f0d-c5f8-4455-b5a4-214a65dca1c1"
   },
   "outputs": [],
   "source": [
    "with open(\"vocab.txt\", \"w\", encoding=\"utf8\") as f:\n",
    "    for w, i in vocab2id.items():\n",
    "        f.write(w+\"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "uuid": "d4c0bbc4-fcab-43c6-b3f6-bb5543bfae75"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>sent1</th>\n",
       "      <th>sent2</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[10, 238, 2, 214]</td>\n",
       "      <td>[3, 5, 2, 17, 150, 5, 214, 7, 10, 1006, 583, 4...</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[102, 153, 32, 2, 7, 72, 591, 6, 134, 3073, 6]</td>\n",
       "      <td>[829, 132, 134, 211, 2, 33]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>[2, 110, 181, 9, 23, 19, 8]</td>\n",
       "      <td>[3, 5, 202, 12, 19, 2, 119, 8]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>[57, 6939, 52, 4]</td>\n",
       "      <td>[68, 570, 52, 4]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>[2, 314, 584]</td>\n",
       "      <td>[212, 1031, 12, 13, 2, 8]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>[2, 38, 47, 22, 18, 8]</td>\n",
       "      <td>[3, 53, 28, 38, 47, 9, 91, 6, 28, 98, 11, 28, ...</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>[2, 18, 2285]</td>\n",
       "      <td>[2, 18, 226]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>[4, 38, 216, 402]</td>\n",
       "      <td>[57, 271, 2, 216, 402]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>[4, 2886, 196, 54, 9, 5, 63, 9, 23, 18, 8]</td>\n",
       "      <td>[4, 54, 18, 9, 7, 17, 8]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>[2, 14, 22, 34, 97, 232]</td>\n",
       "      <td>[2, 377, 6, 14, 26, 12, 327, 13, 6]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                             sent1  \\\n",
       "1                                [10, 238, 2, 214]   \n",
       "2   [102, 153, 32, 2, 7, 72, 591, 6, 134, 3073, 6]   \n",
       "3                      [2, 110, 181, 9, 23, 19, 8]   \n",
       "4                                [57, 6939, 52, 4]   \n",
       "5                                    [2, 314, 584]   \n",
       "6                           [2, 38, 47, 22, 18, 8]   \n",
       "7                                    [2, 18, 2285]   \n",
       "8                                [4, 38, 216, 402]   \n",
       "9       [4, 2886, 196, 54, 9, 5, 63, 9, 23, 18, 8]   \n",
       "10                        [2, 14, 22, 34, 97, 232]   \n",
       "\n",
       "                                                sent2  label  \n",
       "1   [3, 5, 2, 17, 150, 5, 214, 7, 10, 1006, 583, 4...      1  \n",
       "2                         [829, 132, 134, 211, 2, 33]      0  \n",
       "3                      [3, 5, 202, 12, 19, 2, 119, 8]      0  \n",
       "4                                    [68, 570, 52, 4]      0  \n",
       "5                           [212, 1031, 12, 13, 2, 8]      0  \n",
       "6   [3, 53, 28, 38, 47, 9, 91, 6, 28, 98, 11, 28, ...      0  \n",
       "7                                        [2, 18, 226]      0  \n",
       "8                              [57, 271, 2, 216, 402]      0  \n",
       "9                            [4, 54, 18, 9, 7, 17, 8]      0  \n",
       "10                [2, 377, 6, 14, 26, 12, 327, 13, 6]      0  "
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def sent2index(vocab2id, words):\n",
    "    return [vocab2id[w] if w in vocab2id else vocab2id[\"<UNK>\"] for w in words]\n",
    "\n",
    "data_df[\"sent1\"] = data_df[\"sent1\"].apply(lambda x: sent2index(vocab2id, x))\n",
    "data_df[\"sent2\"] = data_df[\"sent2\"].apply(lambda x: sent2index(vocab2id, x))\n",
    "\n",
    "data_df.head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "uuid": "9365445a-ffb5-4800-9ef0-7ba173ec2b0a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.2.0\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "from tensorflow.keras import backend as K\n",
    "\n",
    "print(tf.__version__)\n",
    "\n",
    "class BaseTextCNN(keras.Model):\n",
    "    def __init__(self, filters, kernel_sizes, output_dim, name):\n",
    "        super(BaseTextCNN, self).__init__(name=name)\n",
    "        self.kernel_sizes = kernel_sizes\n",
    "        self.conv_layers = []\n",
    "        self.max_poolings = []\n",
    "        for kernel_size in kernel_sizes:\n",
    "            self.conv_layers.append(\n",
    "                keras.layers.Conv1D(filters=filters, kernel_size=kernel_size, \n",
    "                                activation='relu', padding=\"same\")\n",
    "            )\n",
    "            self.max_poolings.append(keras.layers.GlobalMaxPool1D())\n",
    "        self.concatenate = keras.layers.Concatenate()\n",
    "        self.dense = keras.layers.Dense(output_dim, activation='tanh')\n",
    "        \n",
    "    def call(self, inputs):\n",
    "        convs = []\n",
    "        for i in range(len(self.kernel_sizes)):\n",
    "            x = self.conv_layers[i](inputs)\n",
    "            x = self.max_poolings[i](x)\n",
    "            convs.append(x)\n",
    "        x = self.concatenate(convs)\n",
    "        output = self.dense(x)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "uuid": "1bc2d268-9e7e-4b7c-aff0-10e7afde4f33"
   },
   "outputs": [],
   "source": [
    "max_len = 15\n",
    "vocab_size = len(vocab2id)\n",
    "embedding_size = 128\n",
    "filters = 200\n",
    "kernel_sizes = [3,4,5]\n",
    "output_dim = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "uuid": "0c2a785b-89d9-422a-aefb-42f58ff6971f"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras.layers import Input,Embedding,Dot,Dense\n",
    "from tensorflow.keras.models import Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "uuid": "ba73323a-3bd6-4b4c-a7a9-43179918261f"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras.layers import Dense,Conv1D,GlobalMaxPool1D,concatenate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "uuid": "b17c4bb3-a919-47da-afe0-ddd42f10c18d"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras.layers import Bidirectional,LSTM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "uuid": "39d8f6f0-270a-4098-85c1-96296a5fabd2"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras.layers import Attention"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "uuid": "7606dfd6-8ceb-454d-a807-7ee9cbf34eda"
   },
   "outputs": [],
   "source": [
    "# EMBEDDING_SIZE = 100\n",
    "hidden_size= 64\n",
    "attention_size = 50\n",
    "# hidden_size=64\n",
    "class_nums=2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "uuid": "d65bb706-4017-4ca0-b2a0-ae3958e06ae6"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras import  initializers,regularizers,constraints\n",
    "from  tensorflow.keras.layers import Layer\n",
    "import tensorflow as tf\n",
    "\n",
    "class Attention(Layer):\n",
    "    def __init__(self,\n",
    "                 W_regularizer=None,\n",
    "                 b_regularizer=None,\n",
    "                 W_constraint=None,\n",
    "                 b_constraint=None,\n",
    "                 bias=True,\n",
    "                 **kwargs\n",
    "                 ):\n",
    "        \"\"\"\n",
    "        Keras Layer that implements an Attention mechanism for temporal data.\n",
    "        Supports Masking.\n",
    "        Follows the work of Raffel et al. [https://arxiv.org/abs/1512.08756]\n",
    "        # Input shape\n",
    "            3D tensor with shape: `(samples, steps, features)`.\n",
    "        # Output shape\n",
    "            2D tensor with shape: `(samples, features)`.\n",
    "        :param kwargs:\n",
    "        Just put it on top of an RNN Layer (GRU/LSTM/SimpleRNN) with return_sequences=True.\n",
    "        The dimensions are inferred based on the output shape of the RNN.\n",
    "        Example:\n",
    "            # 1\n",
    "            model.add(LSTM(64, return_sequences=True))\n",
    "            model.add(Attention())\n",
    "            # next add a Dense layer (for classification/regression) or whatever...\n",
    "            # 2\n",
    "            hidden = LSTM(64, return_sequences=True)(words)\n",
    "            sentence = Attention()(hidden)\n",
    "            # next add a Dense layer (for classification/regression) or whatever...\n",
    "        \"\"\"\n",
    "        super(Attention, self).__init__()\n",
    "        self.bias = bias\n",
    "        self.init = initializers.get('glorot_uniform')\n",
    "\n",
    "    def build(self, input_shape):\n",
    "        '''\n",
    "        :param input_shape:\n",
    "        :return:\n",
    "        '''\n",
    "        self.output_dim = input_shape[-1]\n",
    "        self.W = self.add_weight(\n",
    "                                 name='{}_W'.format(self.name),\n",
    "                                 shape=(input_shape[2], 1),\n",
    "                                 initializer=self.init,\n",
    "                                 trainable=True\n",
    "                                 )\n",
    "        if self.bias:\n",
    "            self.b = self.add_weight(\n",
    "                                     name='{}_b'.format(self.name),\n",
    "                                     shape=(input_shape[1], 1),\n",
    "                                     initializer='zero',\n",
    "                                     trainable=True\n",
    "                                     )\n",
    "        else:\n",
    "            self.b = None\n",
    "\n",
    "        self.built = True\n",
    "\n",
    "    def compute_mask(self, inputs, mask=None):\n",
    "        return None\n",
    "\n",
    "    def call(self, inputs, mask=None):\n",
    "        # (N, step, d), (d, 1)  ==>   (N, step, 1)\n",
    "        e = tf.matmul(inputs, self.W, )\n",
    "        if self.bias:\n",
    "            e += self.b\n",
    "        e = tf.tanh(e)\n",
    "        a = tf.nn.softmax(e, axis=1)\n",
    "        # (N, step, d) (N, step, 1) ====> (N, step, d)\n",
    "        c = inputs*a\n",
    "        # (N, d)\n",
    "        c = tf.reduce_sum(c, axis=1)\n",
    "        return c\n",
    "\n",
    "    def get_config(self):\n",
    "        return {'units': self.output_dim}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "uuid": "c412ac2d-e611-46ea-8ca3-d6f49ba9e842"
   },
   "outputs": [],
   "source": [
    "input1=Input(name='sent1',shape=(max_len,))\n",
    "input2=Input(name='sent2',shape=(max_len,))\n",
    "embedding=Embedding(vocab_size,embedding_size)\n",
    "sent1_embed=embedding(input1)\n",
    "sent2_embed=embedding(input2)\n",
    "b1=Bidirectional(LSTM(hidden_size, dropout=0.2, return_sequences=True))(sent1_embed)\n",
    "b2=Bidirectional(LSTM(hidden_size, dropout=0.2, return_sequences=True))(sent2_embed)\n",
    "output_sent1= Attention()(b1)\n",
    "output_sent2= Attention()(b2)\n",
    "cosine_output=Dot(axes=[1,1],normalize=True)([output_sent1,output_sent2])\n",
    "outputs=Dense(1,activation='linear',name=\"output\")(cosine_output)\n",
    "model=Model(inputs=[input1,input2],outputs=outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "uuid": "aad68f2e-1d34-4cf8-b31e-73bd97f16b73"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model\"\n",
      "__________________________________________________________________________________________________\n",
      "Layer (type)                    Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      "sent1 (InputLayer)              [(None, 15)]         0                                            \n",
      "__________________________________________________________________________________________________\n",
      "sent2 (InputLayer)              [(None, 15)]         0                                            \n",
      "__________________________________________________________________________________________________\n",
      "embedding_1 (Embedding)         (None, 15, 128)      1697536     sent1[0][0]                      \n",
      "                                                                 sent2[0][0]                      \n",
      "__________________________________________________________________________________________________\n",
      "bidirectional_2 (Bidirectional) (None, 15, 128)      98816       embedding_1[0][0]                \n",
      "__________________________________________________________________________________________________\n",
      "bidirectional_3 (Bidirectional) (None, 15, 128)      98816       embedding_1[1][0]                \n",
      "__________________________________________________________________________________________________\n",
      "attention_1 (Attention)         (None, 128)          143         bidirectional_2[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "attention_2 (Attention)         (None, 128)          143         bidirectional_3[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "dot (Dot)                       (None, 1)            0           attention_1[0][0]                \n",
      "                                                                 attention_2[0][0]                \n",
      "__________________________________________________________________________________________________\n",
      "output (Dense)                  (None, 1)            2           dot[0][0]                        \n",
      "==================================================================================================\n",
      "Total params: 1,895,456\n",
      "Trainable params: 1,895,456\n",
      "Non-trainable params: 0\n",
      "__________________________________________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "uuid": "c9c7764a-cf08-4843-9736-cf6aead6e05a"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from tensorflow.keras.preprocessing.sequence import pad_sequences\n",
    "\n",
    "def batch_generator(all_data, batch_size, maxlen, shuffle=True):\n",
    "    \"\"\"\n",
    "    :param all_data : all_data整个数据集，包含输入和输出标签\n",
    "    :param batch_size: batch_size表示每个batch的大小\n",
    "    :param shuffle: 是否打乱顺序\n",
    "    :return:\n",
    "    \"\"\"\n",
    "    # 输入all_datas的每一项必须是numpy数组，保证后面能按p所示取值\n",
    "    all_data = [np.array(d) for d in all_data]\n",
    "    # 获取样本大小\n",
    "    data_size = all_data[0].shape[0]\n",
    "\n",
    "    if shuffle:\n",
    "        # 随机生成打乱的索引\n",
    "        p = np.random.permutation(data_size)\n",
    "        # 重新组织数据\n",
    "        all_data = [d[p] for d in all_data]\n",
    "        \n",
    "    batch_count = 0\n",
    "    while True:\n",
    "        # 数据一轮循环(epoch)完成，打乱一次顺序\n",
    "        if batch_count * batch_size + batch_size > data_size:\n",
    "            batch_count = 0\n",
    "            if shuffle:\n",
    "                p = np.random.permutation(data_size)\n",
    "                all_data = [d[p] for d in all_data]\n",
    "        start = batch_count * batch_size\n",
    "        end = start + batch_size\n",
    "        batch_count += 1\n",
    "        batch_data = [d[start: end] for d in all_data]\n",
    "        batch_sent1, batch_sent2, batch_label = batch_data\n",
    "        \n",
    "        batch_sent1_pad = pad_sequences(batch_sent1, maxlen=max_len, padding='post')\n",
    "        batch_sent2_pad = pad_sequences(batch_sent2, maxlen=max_len, padding='post')\n",
    "        \n",
    "        yield [batch_sent1_pad, batch_sent2_pad], batch_label"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "uuid": "0584981c-6b53-4ae7-b772-8b32c97f80cd"
   },
   "outputs": [],
   "source": [
    "sent1_datas = data_df[\"sent1\"].values.tolist()\n",
    "sent2_datas = data_df[\"sent2\"].values.tolist()\n",
    "labels = data_df[\"label\"].values.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "uuid": "19a02484-1889-4bdc-9550-9bd9d86a2125"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train data:  81981 81981 81981\n",
      "val data:  10248 10248 10248\n",
      "test data:  10248 10248 10248\n"
     ]
    }
   ],
   "source": [
    "# 划分训练 测试数据集\n",
    "count = len(labels)\n",
    "idx1, idx2 = int(count*0.8), int(count*0.9)\n",
    "sent1_train, sent2_train = sent1_datas[:idx1], sent2_datas[:idx1]\n",
    "sent1_val, sent2_val = sent1_datas[idx1:idx2], sent2_datas[idx1:idx2]\n",
    "sent1_test, sent2_test = sent1_datas[idx2:], sent2_datas[idx2:]\n",
    "\n",
    "train_labels, val_labels, test_labels = labels[:idx1], labels[idx1:idx2], labels[idx2:]\n",
    "\n",
    "print(\"train data: \", len(sent1_train), len(sent2_train), len(train_labels))\n",
    "print(\"val data: \", len(sent1_val), len(sent2_val), len(val_labels))\n",
    "print(\"test data: \", len(sent1_test), len(sent2_test), len(test_labels))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "uuid": "7c4edf51-b5cd-441a-8a43-87c45b43320d"
   },
   "outputs": [],
   "source": [
    "# batch数据的生成器\n",
    "batch_size = 64\n",
    "maxlen = 15\n",
    "batch_count = int(len(train_labels) / batch_size)\n",
    "batch_gen_train = batch_generator([sent1_train, sent2_train, train_labels], batch_size, max_len)\n",
    "batch_gen_val = batch_generator([sent1_val, sent2_val, val_labels], batch_size, max_len)\n",
    "batch_gen_test = batch_generator([sent1_test, sent2_test, test_labels], batch_size, max_len)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "uuid": "6b8b9459-da32-49aa-926b-1b4d8f3dd469"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:12: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray\n",
      "  if sys.path[0] == '':\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/2\n",
      "1280/1280 [==============================] - 38s 30ms/step - loss: 0.4703 - accuracy: 0.8129 - val_loss: 0.4487 - val_accuracy: 0.8248\n",
      "Epoch 2/2\n",
      "1280/1280 [==============================] - 36s 28ms/step - loss: 0.4439 - accuracy: 0.8153 - val_loss: 0.4376 - val_accuracy: 0.8370\n",
      "50/50 [==============================] - 0s 5ms/step - loss: 0.4294 - accuracy: 0.8416\n",
      "score: 0.4294232130050659 accuracy: 0.8415625095367432\n"
     ]
    }
   ],
   "source": [
    "epochs = 10\n",
    "\n",
    "model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])\n",
    "\n",
    "# 训练模型\n",
    "model.fit(batch_gen_train, \n",
    "                    verbose=1, \n",
    "                    validation_data=batch_gen_val,\n",
    "                    validation_steps=100,\n",
    "                    steps_per_epoch=batch_count, \n",
    "                    epochs=2)\n",
    "\n",
    "# 预测模型\n",
    "score, acc = model.evaluate(batch_gen_test, steps=50, \n",
    "                                      max_queue_size=10, \n",
    "                                      use_multiprocessing=False)\n",
    "print('score:', score, 'accuracy:', acc)\n",
    "\n",
    "# 保存训练好的模型\n",
    "# model.save(\"output/cnndssm_semantic_match.h5\")\n",
    "# model.save_weights(\"output/match_model_weight.h5\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "uuid": "b3680655-f032-4a4d-a19d-02e2c7127895"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sent1: 怎么更改花呗手机号码？\n",
      "sent2: 怎么更改成现在的支付宝的号码手机号？\n",
      "score: [0.31136975]\n"
     ]
    }
   ],
   "source": [
    "def sent2index(vocab2id, words):\n",
    "    return [vocab2id[w] if w in vocab2id else vocab2id[\"<UNK>\"] for w in words]\n",
    "\n",
    "sent1 = \"怎么更改花呗手机号码？\"\n",
    "sent2 = \"怎么更改成现在的支付宝的号码手机号？\"\n",
    "\n",
    "sent1_ids = sent2index(vocab2id, jieba.lcut(sent1))\n",
    "sent2_ids = sent2index(vocab2id, jieba.lcut(sent2))\n",
    "\n",
    "sent1_pad = pad_sequences([sent1_ids], maxlen=max_len, padding='post')\n",
    "sent2_pad = pad_sequences([sent2_ids], maxlen=max_len, padding='post')\n",
    "\n",
    "# model.load_weights(\"output/match_model_weight.h5\")\n",
    "\n",
    "preds = model.predict([sent1_pad, sent2_pad])\n",
    "\n",
    "print(\"sent1: %s\" % sent1)\n",
    "print(\"sent2: %s\" % sent2)\n",
    "print(\"score: %s\" % preds[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "uuid": "c6d564eb-8b5f-4ac8-a115-c178e0c4cd6a"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
